﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using mediaEncoder;
using System.Text.RegularExpressions;
using System.ComponentModel;
using MediaPortal.GUI.Library;

namespace Burn4You
{

    /// <summary>
    /// Info used to convert the media. Most fields can be left blank for their defualts.
    /// </summary>
    public struct EncodeSettings
    {
        /// <summary>
        /// Full path to the media to be encoded.
        /// </summary>
        public String MediaPath;
        /// <summary>
        /// The codec to be converted to.
        /// </summary>
        public String Codec;
        /// <summary>
        /// The Max Bitrate.
        /// </summary>
        public String BitRateMax;
        /// <summary>
        /// Filename of the files to be created.
        /// </summary>
        public String OutputFileName;
        /// <summary>
        /// Full path to the folder where the file should be created
        /// </summary>
        public String OutPutFolder;
        /// <summary>
        /// The type of media your trying to created. E.G. dvd, vcd, svcd, bluray. Add "-pal" or "-ntsc" to use those standards. This variable can be used to set the default encoding options for the specified target.
        /// </summary>
        public String Target;
        /// <summary>
        /// An ID to give to the file. The encoder will give the assoicated encoded file the same ID. This allows the you to
        /// work out which returned encoded file relates to the files given to it.
        /// </summary>
        public int ID;
    }

    public struct EncoderUpdate
    {
        public int Frame;
        public int FPS;
        public decimal Quantanizer;
        public double CurrentSize;
        public decimal CurrentTime;
        public decimal CurrentBitrate;
        public int dup;
        public int drop;
        public double EstimatedSize;
        public double AvgBitrate;
        public int AvgFPS;
        public Int32 PercentageComplete;
    }

    public struct EncoderResult
    {
        public String MediaFile { get; set; }
        public System.Drawing.Image PreviewImage { get; set; }
        public string RawOutput { get; set; }
        public bool Success { get; set; }
    }

    public class EncoderHelper
    {
        private MediaEncoder encoder;

        private List<int> ListFPS;
        private List<decimal> ListCurrentBitrate;

        public delegate void FileFinishedHandler(EncoderResult result);
        public event FileFinishedHandler FileFinishedEvent;

        public delegate void AllFilesFinishedHandler(List<EncoderResult> results);
        public event AllFilesFinishedHandler AllFilesFinishedEvent;

        public delegate void EncoderUpdateHandler(EncoderUpdate update);
        public event EncoderUpdateHandler EncoderUpdateEvent;

        public List<EncodeSettings> FilesToEncode;

        public bool ShowGUI = false;

        /// <summary>
        /// Duration in miliseconds
        /// </summary>
        private double Duration;

        BackgroundWorker EncodeFilesWorker;

        public EncoderHelper()
        {
            ListFPS = new List<int>();
            ListCurrentBitrate = new List<decimal>();
            FilesToEncode = new List<EncodeSettings>();

            EncodeFilesWorker = new BackgroundWorker();
            EncodeFilesWorker.WorkerReportsProgress = true;
            EncodeFilesWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(EncodeFilesWorker_RunWorkerCompleted);
            EncodeFilesWorker.DoWork += new DoWorkEventHandler(EncodeFilesWorker_DoWork);

        }

        public void Stop()
        {
            encoder.Stop();
            Duration = 0;
        }

        void EncodeFilesWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            List<EncoderResult> results = (List<EncoderResult>)e.Result;

            if(AllFilesFinishedEvent != null)
                AllFilesFinishedEvent(results);
        }

        void EncodeFilesWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            List<EncodeSettings> files = (List<EncodeSettings>)e.Argument;
            List<EncoderResult> results = new List<EncoderResult>();

            if(ShowGUI)
                ShowProgressWindow();

            foreach (EncodeSettings file in files)
            {
                Utils.Burn4YouLog.Write("Encoder Helper: Starting encoder for file " + file.MediaPath);

                MediaInfo mi = MediaInfo.GetInstance();

                mi.Open(file.MediaPath);

                String temp = mi.getDuration();
                Duration = Double.Parse(temp);

                EncoderResult result = Encode(file);

                if(FileFinishedEvent != null)
                    FileFinishedEvent(result);

                Utils.Burn4YouLog.Write("Encoder Helper: Encoder finished for file " + file.MediaPath);
                results.Add(result);
            }

            e.Result = results;
        }

        private void ShowProgressWindow()
        {
            GUIWindowManager.ActivateWindow((int)Screen.ProgressScreen);
        }

        public void EncodeFiles(List<EncodeSettings> files)
        {
            foreach (EncodeSettings file in files)
            {
                FilesToEncode.Add(file);
            }

            if (!EncodeFilesWorker.IsBusy)
                EncodeFilesWorker.RunWorkerAsync(FilesToEncode);
        }

        private EncoderResult Encode(EncodeSettings es)
        {
            Utils.Burn4YouLog.Write("Encoder Helper: Starting an encode for file \"" + es.MediaPath + "\"");

            if (encoder == null)
            {
                encoder = new MediaEncoder(Burn4YouPlugin.settings.ExeLocFFmpeg, Burn4YouPlugin.settings.WorkingFolder);
                encoder.Log += new MediaEncoder.LogHandler(encoder_Log);
                encoder.FFMpegUpdate += new MediaEncoder.FFMpegUpdateHandler(encoder_FFMpegUpdate);
                encoder.Initialize();
            }

            EncodeInfo ei = new EncodeInfo();

            #region Fill Encoderinfo
            
            if (es.MediaPath != null)
                ei.MediaPath = es.MediaPath;

            if (es.Codec != null)
                ei.Codec = es.Codec;

            if (es.BitRateMax != null)
                ei.BitRateMax = es.BitRateMax;

            if (es.OutputFileName != null)
                ei.OutputFileName = es.OutputFileName;

            if (es.OutPutFolder != null)
                ei.OutPutFolder = es.OutPutFolder;
            else
                ei.OutPutFolder = Burn4YouPlugin.settings.WorkingFolder;

            if (es.Target != null)
                ei.Target = es.Target;

            #endregion

            OutputPackage op = encoder.ConvertMedia(ei);

            EncoderResult result = new EncoderResult();

            result.MediaFile = op.MediaFile;
            result.PreviewImage = op.PreviewImage;
            result.RawOutput = op.RawOutput;
            result.Success = op.Success;

            return result;
        }


        void encoder_FFMpegUpdate(string Message)
        {
            Utils.Burn4YouLog.Write("Encoder Helper: FFMpeg output: " + Message);
            EncoderUpdate update = readFFMpegUpdate(Message);

            GUIMessage message = new GUIMessage();
            message.TargetWindowId = (int)Screen.ProgressScreen;
            message.SendToTargetWindow = true;
            message.Label = "Calling Object";
            message.Object = this;
            GUIWindowManager.SendMessage(message);

            if(EncoderUpdateEvent != null)
                EncoderUpdateEvent(update);
        }

        void encoder_Log(string Message)
        {
            Utils.Burn4YouLog.Write("Encoder Helper: " + Message);
        }

        EncoderUpdate readFFMpegUpdate(String UpdateString)
        {
            EncoderUpdate update = new EncoderUpdate();

            Regex re;
            Match m;
            String raw;

            re = new Regex("frame=\\s*\\d*\\s");
            m = re.Match(UpdateString);

            if (m.Success)
            {
                raw = m.Value;
                raw = raw.Remove(0, 7);
                raw = raw.Trim();

                update.Frame = int.Parse(raw);
            }

            re = new Regex("fps=\\s*\\d*\\s");
            m = re.Match(UpdateString);

            if (m.Success)
            {
                raw = m.Value;
                raw = raw.Remove(0, 4);
                raw = raw.Trim();

                update.FPS = int.Parse(raw);
            }

            re = new Regex("q=\\d.\\d\\s");
            m = re.Match(UpdateString);

            if (m.Success)
            {
                raw = m.Value;
                raw = raw.Remove(0, 2);
                raw = raw.Trim();

                if(!decimal.TryParse(raw, out update.Quantanizer))
                    Utils.Burn4YouLog.Write("Encoder Helper: Could not parse quantalizer");
            }

            re = new Regex("size=\\s*\\d*kB\\s");
            m = re.Match(UpdateString);

            if (m.Success)
            {
                raw = m.Value;
                raw = raw.Remove(0, 5);
                raw = raw.Trim();
                raw = raw.Remove(raw.Length - 2, 2);

                if (!double.TryParse(raw, out update.CurrentSize))
                    Utils.Burn4YouLog.Write("Encoder Helper: Could not parse current size");
            }

            re = new Regex("time=\\d*.\\d*\\s");
            m = re.Match(UpdateString);

            if (m.Success)
            {
                raw = m.Value;
                raw = raw.Remove(0, 5);
                raw = raw.Trim();

                if (!decimal.TryParse(raw, out update.CurrentTime))
                    Utils.Burn4YouLog.Write("Encoder Helper: Could not parse current time");
            }

            re = new Regex("bitrate=\\s?\\d*.\\dkbits/s\\s");
            m = re.Match(UpdateString);

            if (m.Success)
            {
                raw = m.Value;
                raw = raw.Remove(0, 8);
                raw = raw.Trim();
                raw = raw.Remove(raw.Length - 7, 7);

                if (!decimal.TryParse(raw, out update.CurrentBitrate))
                    Utils.Burn4YouLog.Write("Encoder Helper: Could not parse current bitrate");
            }

            re = new Regex("dup=\\d");
            m = re.Match(UpdateString);

            if (m.Success)
            {
                raw = m.Value;
                raw = raw.Remove(0, 4);
                raw = raw.Trim();

                if (!int.TryParse(raw, out update.dup))
                    Utils.Burn4YouLog.Write("Encoder Hepler: Could not parse dup");
            }

            re = new Regex("drop=\\d*");
            m = re.Match(UpdateString);

            if (m.Success)
            {
                raw = m.Value;
                raw = raw.Remove(0, 5);
                raw = raw.Trim();

                if (!int.TryParse(raw, out update.drop))
                    Utils.Burn4YouLog.Write("Encoder Helper: Could not parse drop");
            }

            if (ListCurrentBitrate.Count > 10)
                ListCurrentBitrate.RemoveAt(0);

            ListCurrentBitrate.Add(update.CurrentBitrate);

            if (ListFPS.Count > 10)
                ListFPS.RemoveAt(0);

            ListFPS.Add(update.FPS);

            double TotalBitrate = 0;
            int BitrateWith0 = 0;
            foreach (double d in ListCurrentBitrate)
            {
                if (d == 0)
                    BitrateWith0++;

                TotalBitrate += d;
            }

            int TotalFPS = 0;
            int FPSWith0 = 0;
            foreach (int fps in ListFPS)
            {
                if (fps == 0)
                    FPSWith0++;

                TotalFPS += fps;
            }

            int TotalListCurrentBitrate = (ListCurrentBitrate.Count - BitrateWith0);
            int TotalListFPS = (ListFPS.Count - FPSWith0);

            if(TotalListCurrentBitrate != 0)
                update.AvgBitrate = TotalBitrate / (ListCurrentBitrate.Count - BitrateWith0);

            if(TotalListFPS != 0)
                update.AvgFPS = TotalFPS / (ListFPS.Count - FPSWith0);

            

            DecimalConverter decimalcon = new DecimalConverter();
            Double time = (Double)decimalcon.ConvertTo(update.CurrentTime, typeof(Double));

            Int32Converter intcon = new Int32Converter();

            update.PercentageComplete = (int)intcon.ConvertTo(((time * 1000) / Duration) * 100, typeof(int));

            return update;
            
            /*String Update = "";

            Update += "Encoder: Frame is: " + update.Frame;
            Update += " Encoder: FPS is: " + update.FPS;
            Update += " Encoder: Quantalizer is: " + update.Quantanizer;
            Update += " Encoder: Size is: " + update.CurrentSize;
            Update += " Encoder: Time is: " + update.CurrentTime;
            Update += " Encoder: Bitrate is: " + update.CurrentBitrate;
            Update += " Encoder: Dup is: " + update.dup;
            Update += " Encoder: Drop is: " + update.drop;
            Update += " Encoder: Average FPS is: " + update.AvgFPS;
            Update += " Encoder: Average Bitrate is: " + update.AvgBitrate;

            Utils.Burn4YouLog.Write("Encoder Helper: FFMpeg Update: " + Update);*/
        }
    }
}
